/**
 * 
 */
package playGround.dungeon;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Map;
import java.util.Iterator;
import java.util.Set;

import playGround.square.Rock;
import playGround.square.Scale;
import playGround.square.Square;
import be.kuleuven.cs.som.annotate.Basic;
import be.kuleuven.cs.som.annotate.Raw;

/**
 * @invar the maxDimensions of this dungeon
 * 			|canHaveAsMaxDimensions(getMaxDimensions)
 * @author Niels Claeys, Maarten Christiaen
 * @version 2.3
 */
public abstract class Dungeon<S extends Square> {
	
	/**
	 * Variable representing the max X, Y and Z values a Square can have in this dungeon.
	 */
	private Position maxDimensions;
	/**
	 * Variable representing the maximum amount of slippery Squares this dungeon can have.
	 */
	private BigDecimal maxSlipperySquares;
	/**
	 * Variable registering the state of this dungeon (terminated or not).
	 */
	private boolean isTerminated;
	/**
	 * Variable registering whether or not this dungeon is initialized.
	 */
	private boolean initialized=false;
	/**
	 * Variable containing the superDungeon of this dungeon.
	 */
	private Dungeon<? super S> superDungeon;
	/**
	 * 
	 * @param maxDimensions the biggest dimensions possible
	 * @post ...
	 * 			| new.getInitialized() == true
	 * @post ...
	 * 			| new.getMaxDimensions()==maxDimensions
	 * @post ...
	 * 			| new.getMaxSlipperySquares() == new BigDecimal(0.2)
	 */
	@Raw
	public Dungeon(Position maxDimensions){
		setMaxDimensions(maxDimensions);
		maxSlipperySquares = new BigDecimal(0.2);
		this.initialized=true;
	}
	/**
	 * 
	 * @return the max dimensions of the dungeon.
	 */
	@Basic @Raw
	public Position getMaxDimensions() {
		return (new Position(maxDimensions));
	}
	
	/**
	 * 
	 * @return the maximum of slippery squares
	 * 			| result == new BigDecimal(0.2)
	 */
	@Basic
	public BigDecimal getMaxSlipperySquares(){
		return new BigDecimal(this.maxSlipperySquares.doubleValue());
	}
	
	/**
	 * 
	 * @return whether or not the dungeon is initialized
	 */
	@Basic
	public boolean getInitialized(){
		return this.initialized;
	}
	/**
	 * 
	 * @param maxdimensions of this dungeon
	 * @post ...
	 * 		| new.getMaxDimensions() == maxDimensions
	 * @throws IllegalArgumentException ...
	 * 		| ! canHaveAsMaxDimensions(maxDimensions)
	 */
	@Raw
	public void setMaxDimensions(Position maxdimensions)throws IllegalArgumentException{
		if(!canHaveAsMaxDimensions(maxdimensions))
			throw new IllegalArgumentException();
		this.maxDimensions=maxdimensions;
	}
	/**
	 * returns if position is a valid position
	 * @param position ...
	 * @return
	 * 		 	| if(position == null)
	 * 			| then result == false
	 * 			| else if(position.getXValue()<0|| position.getYValue()<0||position.getZValue()<0)
	 * 			| then result == false
	 * 			| else if(position.getXValue()==position.getYValue()&&position.getYValue()==position.getZValue())
	 * 			| then result == false
	 * 			| else if(position.getXValue()>getMaxDimensions().getXValue()||position.getYValue()>getMaxDimensions().getYValue()||position.getZValue()>getMaxDimensions().getZValue())
	 * 			| then result == false
	 */
	public boolean canHaveAsPosition(Position position){
		if(position==null)
			return false;
		if(position.getXValue()<0|| position.getYValue()<0||position.getZValue()<0)
			return false;
		if(position.getXValue()>getMaxDimensions().getXValue()||position.getYValue()>getMaxDimensions().getYValue()||position.getZValue()>getMaxDimensions().getZValue())
			return false;
		return true;
	}
	/**
	 * 
	 * @param maxDimensions
	 * @return
	 * 			| if(maxDimensions==null)
	 * 			| then result == false
	 * 			| else if(maxDimensions.getXValue()<0|| maxDimensions.getYValue()<0||maxDimensions.getZValue()<0)
	 * 			| then result == false
	 * 			| else if (this.getInitialized()&&maxDimensions.getXValue()>=this.getMaxDimensions().getXValue()&&maxDimensions.getYValue()>=this.getMaxDimensions().getYValue()&&maxDimensions.getZValue()>=this.getMaxDimensions().getZValue())
	 * 			| then result == false
	 */
	@Raw
	public boolean canHaveAsMaxDimensions(Position maxDimensions){
		if(maxDimensions==null)
			return false;
		if(maxDimensions.getXValue()<0|| maxDimensions.getYValue()<0||maxDimensions.getZValue()<0)
			return false;
		if (getInitialized()&&maxDimensions.getXValue()>=this.getMaxDimensions().getXValue()&&maxDimensions.getYValue()>=this.getMaxDimensions().getYValue()&&maxDimensions.getZValue()>=this.getMaxDimensions().getZValue())
			return false;
		return true;
	}

	/**
	 * terminates the dungeon
	 * @post ...
	 * 		| new.isTerminated()==true
	 */
	public void terminate(){
		isTerminated = true;
	}
	/**
	 * returns whether or not the dungeon is terminated.
	 * @return if this dungeon is terminated
	 */
	@Basic
	public boolean isTerminated(){
		return this.isTerminated;
	}
	/**
	 * This method returns the shaft and levels from the dungeon.
	 * @return	|Set<Dungeon<? extends S>> shaftsAndLevels = new HashSet<Dungeon<? extends S>>()
	 * 			|for each dungeon in shaftsAndLevels
	 * 			|	dungeon instanceof NonCompositeDungeon
	 * 			|result==shaftsAndLevels
	 */
	public abstract Set<Dungeon<? extends S>> getShaftsAndLevels();

	/**
	 * This method removes a square
	 * @param 	positionToBeRemoved the position from the square you want to remove
	 */
	public abstract void removeSquare(Position positionToBeRemoved);
	/**
	 * This method returns an iterator that returns a square that is in the dungeon
	 * @return	|result== new Iterator<Object>()
	 */
	public abstract Iterator<Object> getSquares();
	/**
	 * This method gives the superDungeon from a dungeon
	 * @return the superDungeon of this dungeon
	 */
	public Dungeon<? super S> getSuperDungeon(){
		return this.superDungeon;
	}
	/**
	 * this method sets the superDungeon to a specific value
	 * @param dungeon
	 * @post new.getSuperDungeon()==dungeon
	 */
	public void setSuperDungeon(Dungeon<? super S> dungeon){
		this.superDungeon=dungeon;
	}
	/**
	 * This method gets the rootDungeon from a dungeon
	 * @return 	|if(this.getSuperDungeon()!=null)
	 * 			| then result==this.getSuperDungeon().getRootDungeon()
	 * 			|else
	 * 			| then result==this
	 */
	public Dungeon<? super S> getRootDungeon(){
		if(this.getSuperDungeon()!=null)
			return this.getSuperDungeon().getRootDungeon();
		else 
			return this;
	}
	/**
	 * This method gets the squares that satisfy the condition temperature >=200 and square is rock
	 * @return
	 * 		|result== Set<Square>
	 * 		| for each square in result 
	 * 		| this.dungeonHasSquare(square)
	 * @return
	 * 		| for each square in result 
	 * 		| Scale.changeScale(square.getTemperatureScale(), Scale.CELCIUS, square.getTemperature()>=200
	 * 		| square instanceof Rock
	 */
	
	public Set<Square> getSquaresOnConditionRockTemperature(){
		Dungeon<S> dungeon = this;
		Set<Square> squaresSatisfyCondition = new HashSet<Square>();
		Iterator<Object> squares = dungeon.getSquares();
		while(squares.hasNext()){
			Square next = (Square) squares.next();
			int temperature = Scale.changeScale(next.getTemperatureScale(), Scale.CELCIUS, next.getTemperature());
			if(next instanceof Rock && temperature >=200){
				squaresSatisfyCondition.add(next);
			}
		}
		return squaresSatisfyCondition;
	}
	/**
	 * This method filters the squares on a specified condition
	 * @pre
	 * 		| form must be <Condition> <Boolean Operator> <Value> with one space in between
	 * @param string : the condition you want to check with the squares
	 * 				| its form must be <Condition> <Boolean Operator> <Value> with one space in between
	 * @return
	 * 		| Set<Square> that satisfy the condition given in the string
	 */
	public Set<Square> filterSquaresOnCondition(String string){
		ArrayList<String> methods = new ArrayList<String>(Arrays.asList(string.split(" ")));
			String methodInput = methods.get(0);
			String operatorInput = methods.get(1);
			String operandInput = methods.get(2);
			Method method = null;
			try {
				method = Square.class.getMethod(methodInput);
			} catch (SecurityException e) {
			} catch (NoSuchMethodException e) {
			}
			if( ! Arrays.asList(Square.class.getMethods()).contains(method)){
				throw new IllegalArgumentException();
			}
			if( (method.isVarArgs()) ? (method.getParameterTypes().length !=1):(method.getParameterTypes().length != 0) ){
				throw new IllegalArgumentException();
			}
			if(method.getReturnType() == void.class){
				throw new IllegalArgumentException();
			}
			Set<Square> selectedSquares = getSquaresOnCriteria(method,operatorInput,operandInput);
			return selectedSquares;
	}
	/**
	 * This method searches for a square on the criteria
	 * @param method you want to invoke on the object
	 * @param segment2 is the boolean operator
	 * @param segment is the value you want to compare it with
	 * @return 
	 * 		| a Set of squares that satisfy the condition given by segment2 and segment
	 * 		
	 */
	
	@SuppressWarnings({ "unchecked" })
	private Set<Square> getSquaresOnCriteria(Method method,String segment2, String segment){
		Iterator<Object> squares = null;
		Dungeon<Square> dungeon = null;
		if(NonCompositeDungeon.class.isInstance(this)){
			dungeon = (NonCompositeDungeon<Square>) this;
		}
		else{
			dungeon = (CompositeDungeon<Square>) this;
		}
		Set<Square> allSquares = new HashSet<Square>();
		squares = dungeon.getSquares();
		while(squares.hasNext()){
			Square next = (Square) squares.next();
			allSquares.add(next);
		}
		int segment3 = 0;
		Set<Square> squaresSatisfyCondition = new HashSet<Square>();
		for(Square square: allSquares){
			int result = 0;
			try {
				segment3 = Integer.parseInt(segment);
			} catch (NumberFormatException e) {
				Boolean resultBoolean = null;
				Boolean segmentBoolean = Boolean.parseBoolean(segment);
				try {
					resultBoolean = (Boolean) method.invoke(square);
				} catch (IllegalAccessException e1) {
				} catch (IllegalArgumentException e1) {
				} catch (InvocationTargetException e1) {
					e.printStackTrace();
				}
				if(segmentBoolean== true){
					if(segment2.equals("==")){
						if(resultBoolean == true)
							squaresSatisfyCondition.add(square);
					}
					if(segment2.equals("!=")){
						if(resultBoolean == false)
							squaresSatisfyCondition.add(square);
					}
				}
				if(segmentBoolean==false){
					if(segment2.equals("==")){
						if(resultBoolean == false)
							squaresSatisfyCondition.add(square);
					}
					if(segment2.equals("!=")){
						if( resultBoolean == true)
							squaresSatisfyCondition.add(square);
					}
				}
			}
			try{
				result = (Integer) method.invoke(square);
				}catch(IllegalAccessException exc){
					assert false;
				}
				catch(ClassCastException e){
					segment3++;
					if(segment3 == allSquares.size())
						return squaresSatisfyCondition;
				} catch (IllegalArgumentException e) {
				} catch (InvocationTargetException e) {
				}
				if(segment2.length()==1){
					if(segment2.equals(">")){
						if(result > segment3)
							squaresSatisfyCondition.add(square);
					}
					if(segment2.equals("<")){
						if(result < segment3)
							squaresSatisfyCondition.add(square);
					}
				}
				else if(segment2.length()==2){
					if(segment2.equals(">=")){
						if(result >= segment3)
							squaresSatisfyCondition.add(square);
					}
					if(segment2.equals("<=")){
						if(result <= segment3)
							squaresSatisfyCondition.add(square);
					}
					if(segment2.equals("==")){
						if(result == segment3)
							squaresSatisfyCondition.add(square);
					}
					if(segment2.equals("!=")){
						if(result != segment3)
							squaresSatisfyCondition.add(square);
					}
				}
		}
		return squaresSatisfyCondition;
	}
	/**
	 * 
	 * @return a Map<Position, Square>
	 */
	public abstract Map<Position,Square> getPositionSquares();
	/**
	 * 
	 * @param position the position you want the absolute position of
	 * @param dungeon the dungeon in which the given position is absolute
	 * @return the absolute position of the given position in this dungeon.
	 */
	public abstract Position getAbsolutePosition(Position position,Dungeon<? extends S> dungeon);
	/**
	 * 
	 * @param position the position to check
	 * @param dungeon the dungeon in which the given position is an absolute position
	 * @return if the given position is a valid position in the given dungeon.
	 */
	public abstract boolean canHaveAsPosition(Position position, Dungeon<? extends S> dungeon);
}
